package com.run2code.log.aop;

import com.alibaba.fastjson.JSON;
import com.run2code.log.annotation.BizLog;
import com.run2code.log.handler.ILogRecordService;
import com.run2code.log.model.BizLogInfo;
import com.run2code.log.model.BizLogOps;
import com.run2code.log.model.MethodExecuteResult;
import com.run2code.log.parser.BizLogParser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 业务日志切面
 * 关注公众号【奔跑的码畜】，一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/10/13
 */
@Aspect
@Component
public class BizLogAspect {

    /**
     * 系统日志记录器
     */
    public static Logger log = LoggerFactory.getLogger(BizLogAspect.class);

    private final ILogRecordService logRecordService;

    private final BizLogParser bizLogParser;

    @Resource
    private Environment environment;

    // 参数的2个类，已经在启动时通过框架的配置类，交予了Spring管理
    public BizLogAspect(ILogRecordService logRecordService, BizLogParser bizLogParser) {
        this.logRecordService = logRecordService;
        this.bizLogParser = bizLogParser;
    }


    /**
     * 定义切点
     * 切入包含BizLog和BizLogs注解的方法
     */
    @Pointcut("@annotation(com.run2code.log.annotation.BizLog) || @annotation(com.run2code.log.annotation.BizLogs)")
    public void pointCut() {
    }

    /**
     * 环绕通知
     *
     * @param joinPoint 切点
     * @return {@link Object} 返回结果
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 获取方法
        Method method = methodSignature.getMethod();
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        Object target = joinPoint.getTarget();
        Class<?> targetClass = AopUtils.getTargetClass(target);
        // 获取所有的日志注解
        BizLog[] bizLogs = method.getAnnotationsByType(BizLog.class);

        // 将注解参数解析之后放入到实体内，这里是个集合
        List<BizLogOps> bizLogOpsList = new ArrayList<>();
        for (BizLog bizLog : bizLogs) {
            bizLogOpsList.add(parseLogAnnotation(bizLog));
        }
        // 获取不为空的待解析模板
        List<String> expressTemplate = getExpressTemplate(bizLogOpsList);
        // 获取前置自定义函数值
        Map<String, String> customFunctionExecResultMap = bizLogParser.processBeforeExec(expressTemplate, method, args, targetClass);

        Object result = null;
        MethodExecuteResult executeResult = new MethodExecuteResult(true);

        // 执行目标方法
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            executeResult.exception(e);
        }

        boolean existsNoFailTemp = bizLogOpsList.stream().anyMatch(bizLogOps -> ObjectUtils.isEmpty(bizLogOps.getFail()));
        if (!executeResult.isSuccess() && existsNoFailTemp) {
            log.warn("[{}] 方法执行失败，@BizLog注解中 失败模板没有配置", method.getName());
        } else {
            // 解析SpEl表达式
            Map<String, String> templateMap = bizLogParser.processAfterExec(expressTemplate, customFunctionExecResultMap, method, args, targetClass, executeResult.getErrMsg(), result);
            // 发送日志
            sendLog(bizLogOpsList, result, executeResult, templateMap);
        }
        // 这里要把目标方法的结果抛出来，不然会吞掉异常
        if (!executeResult.isSuccess()) {
            throw executeResult.getThrowable();
        }
        return result;
    }

    /**
     * 发送日志
     *
     * @param bizLogOps
     * @param result
     * @param executeResult
     * @param templateMap
     */
    private void sendLog(List<BizLogOps> bizLogOps, Object result, MethodExecuteResult executeResult, Map<String, String> templateMap) {
        List<BizLogInfo> bizLogInfos = createBizLogInfo(templateMap, bizLogOps, executeResult);
        if (!CollectionUtils.isEmpty(bizLogInfos)) {
            bizLogInfos.forEach(bizLogInfo -> {
                bizLogInfo.setResult(JSON.toJSONString(result));
                // 发送日志（这里其实可以追加参数，判断是否多线程执行，目前是交由子类判断）
                logRecordService.record(bizLogInfo);
            });
        }
    }

    /**
     * 创建操作日志实体
     *
     * @param templateMap
     * @param bizLogOpsList
     * @return
     */
    private List<BizLogInfo> createBizLogInfo(Map<String, String> templateMap, List<BizLogOps> bizLogOpsList, MethodExecuteResult executeResult) {
        return Optional.ofNullable(bizLogOpsList)
                .orElse(new ArrayList<>())
                .stream()
                .filter(bizLogOps -> !"false".equalsIgnoreCase(templateMap.get(bizLogOps.getCondition())))
                .map(bizLogOps -> BizLogInfo.builder()
                        .system(bizLogOps.getSystem() == null || bizLogOps.getSystem().isEmpty() ? environment.getProperty("spring.application.name") : bizLogOps.getSystem())
                        .module(bizLogOps.getModule())
                        .type(bizLogOps.getType())
                        .operator(templateMap.get(bizLogOps.getOperator()))
                        .bizNo(templateMap.get(bizLogOps.getBizNo()))
                        .details(templateMap.get(bizLogOps.getDetails()))
                        .content(executeResult.isSuccess() ? templateMap.get(bizLogOps.getSuccess()) : templateMap.get(bizLogOps.getFail()))
                        .success(executeResult.isSuccess())
                        .errorMsg(executeResult.getErrMsg())
                        .executeTime(executeResult.getExecuteTime())
                        .operateTime(executeResult.getOperateTime())
                        .build())
                .collect(Collectors.toList());
    }

    /**
     * 将注解转为实体
     *
     * @param bizLog bizlog注解
     * @return {@link BizLogOps} BizLogOps-日志操作对象
     */
    private BizLogOps parseLogAnnotation(BizLog bizLog) {
        return BizLogOps.builder()
                .system(bizLog.system())
                .module(bizLog.module())
                .type(bizLog.type())
                .operator(bizLog.operator())
                .bizNo(bizLog.bizNo())
                .success(bizLog.success())
                .details(bizLog.detail())
                .fail(bizLog.fail())
                .condition(bizLog.condition())
                .build();
    }

    /**
     * 获取不为空的待解析模板
     * 从这个List里面我们也可以知道，哪些参数需要符合SpEl表达式
     *
     * @param bizLogOpsList
     * @return
     */
    private List<String> getExpressTemplate(List<BizLogOps> bizLogOpsList) {
        Set<String> set = new HashSet<>();
        for (BizLogOps bizLogOps : bizLogOpsList) {
            set.addAll(Arrays.asList(bizLogOps.getBizNo(), bizLogOps.getDetails(),
                    bizLogOps.getOperator(), bizLogOps.getSuccess(), bizLogOps.getFail(),
                    bizLogOps.getCondition()));
        }
        return set.stream().filter(s -> !ObjectUtils.isEmpty(s)).collect(Collectors.toList());
    }

}
